﻿//-----------------------------------------------------------------------
// <copyright file="AsSystemIShouldScheduleDispatchForAMessageConsumeItFromTheQueueMarkItAndNotSeeItAnymore.cs" company="OOHM PROCESSAMENTO DE DADOS LTDA.">
//     Microsoft Public License (MS-PL) This license governs use of the accompanying
//     software. If you use the software, you accept this license. If you do not
//     accept the license, do not use the software. 1. Definitions The terms "reproduce,"
//     "reproduction," "derivative works," and "distribution" have the same meaning
//     here as under U.S. copyright law. A "contribution" is the original software,
//     or any additions or changes to the software. A "contributor" is any person
//     that distributes its contribution under this license. "Licensed patents"
//     are a contributor's patent claims that read directly on its contribution.
//     2. Grant of Rights (A) Copyright Grant- Subject to the terms of this license,
//     including the license conditions and limitations in section 3, each contributor
//     grants you a non-exclusive, worldwide, royalty-free copyright license to
//     reproduce its contribution, prepare derivative works of its contribution,
//     and distribute its contribution or any derivative works that you create.
//     (B) Patent Grant- Subject to the terms of this license, including the license
//     conditions and limitations in section 3, each contributor grants you a non-exclusive,
//     worldwide, royalty-free license under its licensed patents to make, have
//     made, use, sell, offer for sale, import, and/or otherwise dispose of its
//     contribution in the software or derivative works of the contribution in
//     the software. 3. Conditions and Limitations (A) No Trademark License- This
//     license does not grant you rights to use any contributors' name, logo, or
//     trademarks. (B) If you bring a patent claim against any contributor over
//     patents that you claim are infringed by the software, your patent license
//     from such contributor to the software ends automatically. (C) If you distribute
//     any portion of the software, you must retain all copyright, patent, trademark,
//     and attribution notices that are present in the software. (D) If you distribute
//     any portion of the software in source code form, you may do so only under
//     this license by including a complete copy of this license with your distribution.
//     If you distribute any portion of the software in compiled or object code
//     form, you may only do so under a license that complies with this license.
//     (E) The software is licensed "as-is." You bear the risk of using it. The
//     contributors give no express warranties, guarantees or conditions. You may
//     have additional consumer rights under your local laws which this license
//     cannot change. To the extent permitted under your local laws, the contributors
//     exclude the implied warranties of merchantability, fitness for a particular
// </copyright>
//-----------------------------------------------------------------------
namespace AbstractSaaSTemplate.Utility.Tests.Infrastructure.Repositories.IMessagesRepository{    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading;
    using AbstractSaaSTemplate.Domain.Models.Entities;
    using AbstractSaaSTemplate.Domain.Models.ValueObjects;
    using AbstractSaaSTemplate.Domain.Repositories;
    using AbstractSaaSTemplate.Domain.Services;
    using AbstractSaaSTemplate.Kernel.Factories;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    /// <summary>
    /// @14285697
    /// Como sistema, eu deveria poder agendar o envio de um e-mail,
    /// consumí-lo na fila de envios, marcá-lo como enviado e não
    /// visualizá-lo mais na fila de envios.
    /// </summary>
    [TestClass]
    public class TestFor14285697
    {
        #region Properties

        /// <summary>
        /// Gets or sets the service to consume 'Messaging'.
        /// </summary>
        private IMessagingService MessagingService { get; set; }
        
        /// <summary>
        /// Gets or sets the repository to operate 'Templates'.
        /// </summary>
        private ITemplatesRepository TemplatesRepository { get; set; }

        /// <summary>
        /// Gets or sets the repository to operate 'Messages'.
        /// </summary>
        private IMessagesRepository MessagesRepository { get; set; }

        /// <summary>
        /// Gets or sets the repository to operate 'Messaging'.
        /// </summary>
        private IMessagingRepository MessagingRepository { get; set; }

        #endregion

        #region Initialization

        /// <summary>
        /// Prepares the unit test.
        /// </summary>
        [TestInitialize]
        public void Setup()
        {
            TestFor0013453151.EnsureInstallationIsComplete();

            var factory = new ApplicationFactory();

            this.MessagingService = factory.CreateServiceFor<IMessagingService>();
            this.MessagesRepository = factory.CreateRepositoryFor<IMessagesRepository>();
            this.MessagingRepository = factory.CreateRepositoryFor<IMessagingRepository>();
            this.TemplatesRepository = factory.CreateRepositoryFor<ITemplatesRepository>();
        }

        #endregion

        /// <summary>
        /// @14285697
        /// Como sistema, eu deveria poder agendar o envio de um e-mail,
        /// consumí-lo na fila de envios, marcá-lo como enviado e não
        /// visualizá-lo mais na fila de envios.
        /// Passos da Mímica --------------------------------------------------
        /// 1) Criar template de mensagem (e-mail).
        ///     Deve assegurar:
        ///     1.1) Que o template foi criado
        /// 2) Criar mensagem (e-mail) à partir do template.
        ///     Deve assegurar:
        ///     2.1) Que é possível criar uma mensagem (e-mail) à partir do template.
        /// 3) Agendar envio da mensagem para agora
        ///     Deve assegurar:
        ///     3.1) Que a mensagem está disponível na fila de envio
        ///     3.2) Que a mensagem fica indisponível por um numero específico de segundos.
        ///     3.3) Que a mensagem, caso não tenha sido marcada como enviada, reaparecerá na fila depois de alguns instantes.
        /// 4) Marcar mensagem agendada como enviada
        ///     Deve assegurar:
        ///     4.1) Que a mensagem não estará, após marcada como enviada, na lista de mensagens à enviar.
        /// 5) Tenta enviar mensagem
        ///     Deve assegurar:
        ///     5.1) Que não houve erro.
        /// </summary>
        [TestMethod]
        public void AsSystemIShouldScheduleDispatchForAMessageConsumeItFromTheQueueMarkItAndNotSeeItAnymore()
        {
            // 1) 
            var template = this.CreateTemplateAndAssertsThatTemplateWasCreated();

            // 2)
            var message = this.CreateMessageFromTemplateAndAssertsThatItWasInstanciated(template.Key);

            // 3)
            this.ScheduleMessageAndCheckItsAvailabilityAndInvisibilityInTheQueue(message);

            // 4) 
            this.MarksMessageAsSentAndChecksItsUnavailabilityInTheQueue(message);

            // 5)
            this.AttemptsToSendMessage(message);
        }

        /// <summary>
        /// 1) Criar template de mensagem (e-mail).
        ///     Deve assegurar:
        ///     1.1) Que o template foi criado
        /// </summary>
        /// <returns>Creates the template for the message.</returns>
        private Template CreateTemplateAndAssertsThatTemplateWasCreated()
        {
            var template = new Template<EmailMessage>();
            template.Key = "test-template";
            template.CustomData.Subject = "Hey, Mr. {receiver-name}";
            template.CustomData.Body = "We just wanted to know how the Mr. {receiver-name} was! :)";

            this.TemplatesRepository.AddOrUpdateTemplate(template);

            var createdTemplate = this.TemplatesRepository.GetTemplateByKey<EmailMessage>(template.Key);
            Assert.IsNotNull(createdTemplate);
            Assert.IsNotNull(createdTemplate.CustomData);
            Assert.IsInstanceOfType(createdTemplate.CustomData, typeof(MessageCustomData));
            return createdTemplate;
        }

        /// <summary>
        /// 2) Criar mensagem (e-mail) à partir do template.
        ///     Deve assegurar:
        ///     2.1) Que é possível criar uma mensagem (e-mail) à partir do template.
        /// </summary>
        /// <param name="templateKey">The key for the template.</param>
        /// <returns>Returns a e-mail message to be sent.</returns>
        private Message<EmailMessage, EmailAddress> CreateMessageFromTemplateAndAssertsThatItWasInstanciated(string templateKey)
        {
            var emailMessage = this.MessagingService.NewMessageFromTemplate<EmailMessage, EmailAddress>(
                templateKey,
                new EmailAddress("sender@site.com"),
                new[] { new EmailAddress("receiver@site.com") },
                new Dictionary<string, string>() { { "{receiver-name}", "Super Tester da Silva" } });

            Assert.IsNotNull(emailMessage);
            return emailMessage;
        }

        /// <summary>
        /// 3) Agendar envio da mensagem para agora
        ///     Deve assegurar:
        ///     3.1) Que a mensagem está disponível na fila de envio
        ///     3.2) Que a mensagem fica indisponível por um numero específico de segundos.
        ///     3.3) Que a mensagem, caso não tenha sido marcada como enviada, reaparecerá na fila depois de alguns instantes.
        /// </summary>
        /// <param name="message">The e-mail message that will be sent.</param>
        private void ScheduleMessageAndCheckItsAvailabilityAndInvisibilityInTheQueue(
            Message<EmailMessage, EmailAddress> message)
        {
            // we proceed with scheduling the dispatch for the message.
            this.MessagesRepository.ScheduleMessageForDispatch(message);

            // now we will grab a few messages that are scheduled for dispatch
            var messagesToDispatch = this.MessagesRepository.GetMessagesReadyToDispatch(10, 0);

            // here we have check if any of the messages in the list are the
            // message we scheduled for send. Beware: it does not return the
            // entire list of messages to be dispatched, to avoid keeping too
            // many messages invisibility without the need. The message we
            // are attempting to check may not be in this list. this won't
            // happen if this test is not concurring with any other.
            // 3.1) Que a mensagem está disponível na fila de envio
            Assert.IsTrue(messagesToDispatch.Any(d => d.Id == message.Id));

            // 3.2) Que a mensagem fica indisponível por um numero específico de segundos.
            const int SecondsOfInvisibility = 1;
            var otherMessagesToDispatch = this.MessagesRepository.GetMessagesReadyToDispatch(10, SecondsOfInvisibility);
            
            // we get it again, so that the message, invisible for 1 second, should not appear in it
            otherMessagesToDispatch = this.MessagesRepository.GetMessagesReadyToDispatch(10, SecondsOfInvisibility);
            var willReapperOn = DateTime.Now.AddSeconds(SecondsOfInvisibility);
            Assert.IsFalse(otherMessagesToDispatch.Any(d => d.Id == message.Id));

            // 3.3) Que a mensagem, caso não tenha sido marcada como enviada, reaparecerá na fila depois de alguns instantes.
            // now we will wait until the time arrives.
            while (willReapperOn > DateTime.Now)
            {
                Thread.Sleep(1000);
            }

            // load the last messages to dispatch after waiting a little bit,
            // the message should be back to the queue.
            var thirdMessagesToDispatch = this.MessagesRepository.GetMessagesReadyToDispatch(10, 0);
        }

        /// <summary>
        /// 4) Marcar mensagem agendada como enviada
        ///     Deve assegurar:
        ///     4.1) Que a mensagem não estará, após marcada como enviada, na lista de mensagens à enviar.
        /// </summary>
        /// <param name="message">The e-mail message that will be sent.</param>
        private void MarksMessageAsSentAndChecksItsUnavailabilityInTheQueue(
            Message<EmailMessage, EmailAddress> message)
        {
            // marks the message as dispatched
            this.MessagesRepository.MarkMessageAsDispatched(message.Id);

            // and now we check if the message to dispatch is present in the queue.
            // it shouldn't.
            var messagesToDispatch = this.MessagesRepository.GetMessagesReadyToDispatch(10, 0);
            Assert.IsFalse(messagesToDispatch.Any(d => d.Id == message.Id));
        }

        /// <summary>
        /// 5) Tenta enviar mensagem
        ///     Deve assegurar:
        ///     5.1) Que não houve erro.
        /// </summary>
        /// <param name="message">The message to be sent</param>
        private void AttemptsToSendMessage(Message message)
        {
            this.MessagingRepository.SendMessage(message);
        }
    }
}


